/*
 * Created on Apr 11, 2007
 *
 * Copyright (c) 2006-2007 P.J.Leonard
 * 
 * http://www.frinika.com
 * 
 * This file is part of Frinika.
 * 
 * Frinika is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.

 * Frinika is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Frinika; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package com.frinika.audio.analysis;

import java.awt.Dimension;
import java.util.Arrays;

import com.frinika.audio.analysis.gui.CursorObserver;
import uk.org.toot.audio.core.AudioBuffer;
import uk.org.toot.audio.core.AudioProcess;

/**
 *
 *  An attempt at resynthesis from the spectral data.
 *
 *  Use the AudioProcess interface to grab the sound.
 *
 *  The CursorObserver selects the bin to resynthesize.
 *
 *  Doesn't work that well !!!!!
 *
 * @author pjl
 */
public class StaticSpectrogramSynth implements AudioProcess, CursorObserver,
		SpectrogramDataListener {

	private SpectrumDataBuilder data;

	private int nBins;

	private MagFreq magFreq[];

	int peaks[];

	private OscillatorNode oscNext[];

	OscillatorNode oscBank[];   // Array of osccillators.

    /**
     * Contruct a synth that will attempt to resynthesize using a set of oscillators.
     *
     *
     * @param data   Spectral Data
     */
	public StaticSpectrogramSynth(SpectrumDataBuilder data) {
		this.data = data;
		data.addSizeObserver(this);
	}

    /**
     *
     *
     * @return   The current oscillators
     */
	public OscillatorNode[] getOscillatorBank(){
			return oscBank;
	}

	private final void activateOscillatorsAtChunk(
			long chunkPtr) {

		if (!data.validAt(chunkPtr)) {
			System.out.println(" Data not ready at " + chunkPtr);
			for (OscillatorNode osc:oscBank) {
				osc.active=false;
			}
			return;
		}

		float freqs[] = data.getFreqArray();

		// TODO sort out newing

		float magn[] = data.getMagnitudeAt(chunkPtr);
		float pfreq[] = data.getPhaseFreqAt(chunkPtr);
		float phase[] = data.getPhaseFreqAt(chunkPtr);
		// long sampleTime = data.chunkStartInSamples(chunkPtr);

		int cnt = findPeaks(magn, peaks, 0.0f);

		for (int i = 0; i < cnt; i++) {
			magFreq[i].set(peaks[i], magn[peaks[i]]); 
		}

		Arrays.sort(magFreq, 0, cnt);

		System.out.println(" Found " + cnt + " peaks ");

		Arrays.fill(oscNext, null);

		for (int i = 0; i < cnt; i++) {
			int ifreq = magFreq[i].ifreq;
			if (!isMasked(ifreq, magn[ifreq])) {
				int inear = findOscNear(ifreq);
				if (inear != ifreq)
					swap(inear, ifreq);
				oscBank[ifreq].setNext(pfreq[ifreq], magn[ifreq], phase[ifreq]);
				oscNext[ifreq]=oscBank[ifreq];
			}
		}

		
		for (int i=0;i<nBins;i++) {
			if (oscNext[i] == null && oscBank[i].active)  oscBank[i].silence();
		}
		
		// final Vector<OscillatorNode> tmpNextOsc = activeOsc;
		//
		// synchronized(activeOsc) {
		// activeOsc=tmpOsc;
		// tmpOsc=tmpNextOsc;
		// }

		// OscillatorNode ttt[]=oscPrev;
		// oscPrev = oscNext;
		// oscNext = ttt;
//		for (int i=0;i<nBins;i++) {
//			if (oscBank[i].active()) tmpOsc.add(oscBank[i]);
//		}
//		return tmpOsc;
	}

	private void swap(int inear, int ifreq) {
		OscillatorNode nn=oscBank[inear];
		oscBank[inear]=oscBank[ifreq];
		oscBank[ifreq]=nn;
	}

	private int findOscNear(int ifreq) {
		OscillatorNode osc = oscBank[ifreq];
		if (osc.active()) {
			return ifreq;
		}

		if (ifreq < nBins) {
			osc = oscBank[ifreq + 1];
			if (osc.active()) {
				return ifreq + 1;
			}
		}

		if (ifreq > 0) {
			osc = oscBank[ifreq - 1];
			if (osc != null) {
				return ifreq - 1;
			}
		}
		return ifreq;
	}

	final private boolean isMasked(int ifreq, float f) {
		int i1 = ifreq - 2;
		int i2 = ifreq + 2;

		if (i1 < 0)
			i1 = 0;
		if (i2 >= nBins)
			i2 = nBins - 1;

		for (int i = i1; i <= i2; i++) {
			if (oscNext[i] != null)
				return true;
		}

		return false;
	}

	private final int findPeaks(float a[], int peaks[], float threshold) {

		float max = 0.0f;
		int imax = -1;
		int cnt = 0;
		for (int i = 1; i < a.length - 1; i++) {
			if (a[i] < threshold)
				continue;
			if (a[i] >= a[i - 1] && a[i] > a[i + 1]) {
				peaks[cnt++] = i;
			}
		}
		return cnt;

	}

	// class AmpComparator implements Comparator<OscillatorNode> {
	//
	// public int compare(OscillatorNode arg0, OscillatorNode arg1) {
	// int i = Double.compare(arg0.amp, arg1.amp);
	// if (i != 0)
	// return i;
	// System.out.println(" magnitudes are the same ");
	// if (arg0.hashCode() > arg1.hashCode())
	// return 1;
	// return -1;
	// }
	//
	// }

	public void close() {
	}

	public void open() {
	}

	/**
	 * Probably best to call this when not running ?
	 * 
	 * @param ptr
	 */
	void setChunkPtr(long ptr) {
		// System.out.println("OscPlayer set frame " + ptr );
		activateOscillatorsAtChunk(ptr);
		System.out.println("OscPlayer set frame " + ptr);
//		for (Oscillator osc : tmpOsc) {
//			System.out.println(osc.toString());
//		}

	}

	public int processAudio(AudioBuffer buffer) {
		// int size = buffer.getSampleCount();
		buffer.makeSilence();
		for (OscillatorNode osc : oscBank) {
			if (osc.active) osc.processAudio(buffer);
		}
		return AUDIO_OK;
	}

	public void notifyCursorChange(int pix) {
		setChunkPtr(pix);
	}

	public void notifyMoreDataReady() {

	}

    /**
     *
     * @param d
     */
	public void notifySizeChange(Dimension d) {
		System.out.println(" Synth SIZE SET ");

		nBins = d.height;
		oscNext = new OscillatorNode[nBins];
		magFreq = new MagFreq[nBins];
		oscBank = new OscillatorNode[nBins];
		for (int i = 0; i < nBins; i++) {
			magFreq[i] = new MagFreq(i, 0.0f);
			oscBank[i] = new OscillatorNode();
		}
		peaks = new int[nBins];
	}


	final class MagFreq implements Comparable {
		int ifreq;

		float magn;

		public MagFreq(int i, float f) {
			ifreq = i;
			magn = f;
		}

		final public int compareTo(Object o) {
			return Float.compare(magn, ((MagFreq) o).magn);
		}

		final public void set(int i, float f) {
			ifreq = i;
			magn = f;
		}
	};

}
